FE-1604: Allow Voice interruption by speaking - #9619
Conversation
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0772c-6fce-752a-9778-480fa5209c5c Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
PR SummaryMedium Risk Overview Disabling the preference restores half-duplex behavior: Your turn handoff, mic closed during output, and stricter overlap rules for in-flight input. The Realtime session tracks speaking/stopped input items, defers canonical playback until speech ends, and settles cancelled speech requests more explicitly. The bridge adds ordered input draining, pending-interruption slots, and content-free transcription rejection diagnostics. UI exposes the preference via voice controls (persisted with Reviewed by Cursor Bugbot for commit 4e38d31. Bugbot is set up for automated code reviews on this repo. Configure here. |
|
I found four behavioral blockers and one mission-contract gap:
Separately, The architecture and overall branch scope otherwise align well: provider logic remains website-owned, Petrinaut stays provider-neutral, and admission still uses the existing Brunch path. |
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
|
Addressed and pushed in
|
Co-authored-by: Cursor <cursoragent@cursor.com>
|
I still see four code-level gaps:
|
Co-authored-by: Cursor <cursoragent@cursor.com>
|
One high-severity lifecycle gap remains: cancelled
The new cancelled-creation regression covers speech before the collision error, but not either collision-first cleanup or a queued successor with |
Co-authored-by: Cursor <cursoragent@cursor.com>
…ut-voice-interruption
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 350f717. Configure here.
Co-authored-by: Cursor <cursoragent@cursor.com>
…ut-voice-interruption
|
One medium-severity lifecycle gap remains: a retained interruption can remain permanently pending after an empty completed response. In The existing empty-response regression submits the next transcript only after settlement. It does not cover retaining the next transcript before the empty settlement completes. |
This issue is already fixed in the latest PR version. The empty-response path now drains retained interruptions, with a regression test covering the exact scenario. Bugbot and tests pass ✅ |
lunelson
left a comment
There was a problem hiding this comment.
The retained-interruption gap is fixed and covered by the exact empty-response regression. The previously reported collision races are also resolved; focused Voice lifecycle tests pass.

🌟 What is the purpose of this PR?
This PR lets a person interrupt Voice assistant playback by speaking without losing the interrupting utterance. It keeps Your turn as an optional half-duplex fallback and rejects likely prompt-regurgitation or assistant-echo transcripts before they become answers.
This is an independent semantic recut of the interruption-only delta from the stale, conflicting #9550, based on post-deployment
mainatef0f444987. It does not merge or carry the old branch's unrelated Brunch stack. The automated proof establishes protocol ordering, admission, retention, UI preference, and package integrity; it does not establish real microphone latency or acoustic echo behavior.🔗 Related links
🚫 Blocked by
main🔍 What does this change?
speech_started → response.cancel → output_audio_buffer.clear; interruption never clears the input buffer or waits for transcript validation.🏗️ Agent notes
Recut provenance
The stale PR head
f69ac17034contained four interruption product commits after its FE-1580 parent. Those commits were applied onto currentmain; range comparison found one cancellation-settlement hunk already present onmain, with the remaining semantic patches unchanged. The current NodeNext website boundary additionally required the new shared transcription import to use its.jsextension.Constraints retained
Fog-line and stop conditions
Browser echo cancellation may still permit a false VAD event to stop playback before the completed transcript is rejected. Stop or reorient if current-main behavior clears the interrupting input, delays cancellation, admits an interruption twice, erases an earlier retained answer, or fails to restore acknowledged half-duplex handoff when the preference is disabled.
The complete live mission contract is in
libs/@hashintel/brunch-agent/MISSION.md.Pre-Merge Checklist 🚀
🚢 Has this modified a publishable library?
📜 Does this require a change to the docs?
apps/petrinaut-website/README.md,libs/@hashintel/petrinaut/docs/ai-assistant.md, andlibs/@hashintel/brunch-agent/docs/adr/0009-openai-voice-ui-turn-shell.md.🕸️ Does this require a change to the Turbo Graph?
set-state-in-effectwarning in the Voice control component; there are zero errors.🛡 What tests cover this?
Coverage includes immediate cancellation and input preservation; duplicate completion; delayed and pending admission; follow-on canonical speech; retained-answer display; prompt-regurgitation and active-playback self-echo rejection; short novel answers; unchanged ordinary capture; preference/UI behavior; and lifecycle cleanup.
Fresh verification on this recut:
@apps/petrinaut-websitesuite: 41 files, 417 tests passed.@hashintel/petrinautsuite: 94 files, 800 tests passed.git diff --checkpassed.❓ How to test this?
📹 Demo
The original interruption-only capture remains representative of this semantic recut:
fe-1604-voice-interruption-by-speaking.mp4